#include "rpi.h"
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>

#include "unistd.h"
#include <fcntl.h>
#include <poll.h>
#include <bcm2835.h>

using namespace std;

int  RPi::m_file;
int  RPi::pinfile[30];
RPi* RPi::m_instance = 0;

RPi* RPi::getInstance()
{
  if (m_instance == 0)
  {
    printf("RPi: getInstance() called without prior initialization!\n");
    exit(1);
  }
  return m_instance;
}
void RPi::initialize()
{
  m_instance = new RPi();

  struct timeval t;
  gettimeofday(&t, 0x0);

  getInstance()->time_sec = t.tv_sec;
  getInstance()->time_usec = t.tv_usec;
  
  printf("RPi: start time %lds, %ldus\n", getInstance()->time_sec, getInstance()->time_usec);


  // ---------------------

  char devname[256];
  sprintf(devname, "/dev/i2c-0");
  m_file = open(devname, O_RDWR);
  if (m_file == -1)
  {
      printf("Rpi: error opening the i2c bus file /dev/i2c-0\n");
      exit(1);
  }

  // ------------------

  // needed for  io
  if (!bcm2835_init())
  {
    printf("RPi: unable to setup bcm2835 gpio \n");
    exit(1);
  }

  // ------------

  for (int i = 0; i < 30; i++)
  {
    pinfile[i] = 0;
  }
}

void RPi::finalize()
{
  close(m_file);
  for (int i = 0; i < 30; i++)
  {
    if (pinfile[i] != 0)
      close(pinfile[i]);
  }

}

long RPi::usec()
{
  struct timeval t;
  gettimeofday(&t, 0x0);
  return 1000000*(t.tv_sec - m_instance->time_sec)+(t.tv_usec - m_instance->time_usec);
}

long RPi::msec()
{
  struct timeval t;
  gettimeofday(&t, 0x0);
  return 1000*(t.tv_sec - m_instance->time_sec)+(t.tv_usec - m_instance->time_usec)/1000;
}

long RPi::sec()
{
  struct timeval t;
  gettimeofday(&t, 0x0);
  return (t.tv_sec - m_instance->time_sec)+(t.tv_usec - m_instance->time_usec)/1000000;
}

int RPi::i2cFile()
{
  return m_file;
}

void RPi::setupInterrupt(int pin, InterruptTrigger it)
{
  char cmd[256];
  sprintf(cmd, "echo '%d' > /sys/class/gpio/export", pin);
  if (system(cmd) < 0)
  {
    printf("RPi:: Failed setting up an interrupt on pin %d", pin);
    exit(1);
  }
  sprintf(cmd, "chown fen:gpio /sys/devices/virtual/gpio/gpio%d/edge", pin);
  if (system(cmd) < 0)
  {
    printf("RPi: Failed to change ownership of edge on pin %d", pin);
    exit(1);
  }
  sprintf(cmd, "chmod 660 /sys/devices/virtual/gpio/gpio%d/edge", pin);
  if (system(cmd) < 0)
  {
    printf("RPi: Failed to change edge permissions on pin %d", pin);
    exit(1);
  }
  char trigger[20];
  switch (it)
  {
    case Rising:
      sprintf(trigger, "rising");
      break;
    case Falling:
      sprintf(trigger, "falling");
      break;
    case Both:
      sprintf(trigger, "both");
      break;
    case None:
      sprintf(trigger, "none");
      break;
  }

  sprintf(cmd, "echo %s > /sys/devices/virtual/gpio/gpio%d/edge", trigger, pin);
  if (system(cmd) < 0)
  {
    printf("failed to change edge settings to '%s' on pin %d", trigger, pin);
    exit(1);
  }

  // -- open pin file for triggering

  char devname[256];
  sprintf(devname, "/sys/class/gpio/gpio%d/value", pin);
  pinfile[pin] = open(devname, O_RDONLY);
  if (pinfile[pin] < 0)
  {
    printf("RPi: error opening pin %d file for interrupt \n", pin);
    exit(1);
  }
  else
  {
    printf("RPi: success setting up an interrupt on pin %d\n", pin);
  }
}

void RPi::setupOutput(int pin)
{
  bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP);
}
void RPi::setupInput(int pin)
{
  bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_INPT);
}

void RPi::writeOutput(int pin, PinLevel po)
{
  if (po == High)
    bcm2835_gpio_write(pin, HIGH);
  else
    bcm2835_gpio_write(pin, LOW);
}

RPi::PinLevel RPi::readInput(int pin)
{
  uint8_t v = bcm2835_gpio_lev(pin);
  if (v == 1)
    return RPi::High;
  return RPi::Low;
}

int RPi::pollInterrupt(int pin, int timeout)
{
  struct pollfd pfd;
  int ret;

  pfd.fd = pinfile[pin];
  pfd.events = POLLPRI;
  lseek(pinfile[pin], 0, SEEK_SET);
  ret=poll(&pfd, 1, timeout);
  if (ret < 0)
  {
    printf("RPi: interrupt poll on pin %d failed\n", pin);
    return -1;
  }
  if (ret == 0) // timeout
    return -2;

  // read the current value
  char buf = 0;
  usleep(100);
  ret = read(pinfile[pin], &buf, 1);

  if (ret < 0) 
  {
    printf("RPi: read pin value error on pin %d\n", pin);
    return -1;
  }
  printf("%d\n", buf);
  if (buf == '0')
    return 0;
  if (buf == '1')
    return 1;
  return 0;
}

bool RPi::hasOption(const string &opt)
{
  return false;
}
